﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;

namespace Widgetsphere.Core.Util
{
	public static class LINQDynamicCompile
	{
		private static readonly Dictionary<string, MethodInfo> queryDictionary = new Dictionary<string, MethodInfo>();

		public static MethodInfo GetLINQMethodPaged(string linq, string businessNamespace, string objectName)
		{
			return GetLINQMethodPaged(linq, businessNamespace, objectName, new System.Reflection.Assembly[] { });
		}

		public static MethodInfo GetLINQMethodPaged(string linq, string businessNamespace, string objectName, System.Reflection.Assembly[] assemblies)
		{
			var hash = HashHelper.ComputeHash(linq + "|" + businessNamespace + "|" + objectName + "|Paged");
			hash = DateTime.Now.ToString("HHmmss.fff"); //TEMPTEMPTEMPTEMPTEMP

			MethodInfo method = null;
			if (!queryDictionary.ContainsKey(hash))
			{
				method = LINQDynamicCompile.CreateScriptClassPaged(Guid.NewGuid(), businessNamespace, linq, objectName, assemblies);
				queryDictionary.Add(hash, method);
			}
			else
				method = queryDictionary[hash];
			return method;

		}

		public static MethodInfo GetLINQMethodAll(string linq, string businessNamespace, string objectName)
		{
			var hash = HashHelper.ComputeHash(linq + "|" + businessNamespace + "|" + objectName + "|All");
			hash = DateTime.Now.ToString("HHmmss.fff"); //TEMPTEMPTEMPTEMPTEMP

			MethodInfo method = null;
			if (!queryDictionary.ContainsKey(hash))
			{
				method = LINQDynamicCompile.CreateScriptClassAll(Guid.NewGuid(), businessNamespace, linq, objectName);
				queryDictionary.Add(hash, method);
			}
			else
				method = queryDictionary[hash];
			return method;
		}

		private static MethodInfo CreateScriptClassPaged(System.Guid currentGuid, string businessNamespace, string linq, string objectName, System.Reflection.Assembly[] assemblies)
		{
			var guid = currentGuid.ToString("N");
			var sb = new StringBuilder();
			sb.AppendLine("using System;");
			sb.AppendLine("using System.Linq;");
			sb.AppendLine("using System.Linq.Expressions;");
			sb.AppendLine("using " + businessNamespace + ".Objects;");
			sb.AppendLine("using " + businessNamespace + ".LINQ;");
			sb.AppendLine("namespace Widgetpshere.MemoryGeneration");
			sb.AppendLine("{");
			sb.AppendLine("	public static class " + objectName + "Queries" + guid);
			sb.AppendLine("	{");
			//sb.AppendLine("		public static " + objectName + "Collection ZZ" + guid + "(" + objectName + "Paging paging)");
			sb.AppendLine("		public static " + objectName + "Collection ZZ" + guid + "(Widgetsphere.Core.DataAccess.IPagingObject paging)");
			sb.AppendLine("		{");
			sb.AppendLine("			if (!(paging is " + objectName + "Paging))");
			sb.AppendLine("			{");
			sb.AppendLine("				" + objectName + "Paging newPaging = new " + objectName + "Paging();");
			sb.AppendLine("				newPaging.PageIndex = paging.PageIndex;");
			sb.AppendLine("				newPaging.RecordsperPage = paging.RecordsperPage;");
			//sb.AppendLine("				newPaging.RecordCount = paging.RecordCount;");
			//sb.AppendLine("				if (paging.OrderByList != null)");
			//sb.AppendLine("				{");
			//sb.AppendLine("					foreach (object o in paging.OrderByList)");
			//sb.AppendLine("					{");
			//sb.AppendLine("						newPaging.OrderByList.Add(o as " + objectName + "PagingFieldItem);");
			//sb.AppendLine("					}");
			//sb.AppendLine("				}");
			sb.AppendLine("				paging = newPaging;");
			sb.AppendLine("			}");
			sb.AppendLine();
			sb.AppendLine("			return " + objectName + "Collection.RunSelect(" + linq + ", (" + objectName + "Paging)paging);");
			sb.AppendLine("		}");
			sb.AppendLine("	}");
			sb.AppendLine("}");

			try
			{
				var codeDomProvider = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
				var cp = new CompilerParameters();
				//BusinessClass Assembly
				var businessAssembly = Assembly.LoadWithPartialName(businessNamespace.Remove(businessNamespace.Length - 9));
				var appServerBinDirectory = ConfigurationManager.AppSettings["AppServerBinDirectory"];
				if (string.IsNullOrEmpty(appServerBinDirectory))
				{
					var uri = new Uri(Assembly.GetExecutingAssembly().CodeBase);
					var fi = new FileInfo(uri.LocalPath);
					appServerBinDirectory = fi.Directory.FullName;
				}

				if (!string.IsNullOrEmpty(appServerBinDirectory))
				{
					var loadedFolders = new List<string>();
					//cp.CompilerOptions += string.Format(@",/lib:""{0}""", @"C:\WINDOWS\Assembly\gac_msil\LivePA.Portal\1.0.0.0__1c626ba0d40db45d");
					if (!appServerBinDirectory.ToLower().Contains(@"windows\assembly\gac_msil") && !appServerBinDirectory.ToLower().Contains(@"microsoft.net\assembly"))
					{
						cp.CompilerOptions = string.Format(@"/lib:""{0}""", appServerBinDirectory);
						loadedFolders.Add(appServerBinDirectory);
					}
					else
					{
						var uri = new Uri(businessAssembly.CodeBase);
						var fi = new FileInfo(uri.LocalPath);
						cp.CompilerOptions = String.Format("/lib:\"{0}\"", appServerBinDirectory) + String.Format(" /lib:\"{0}\"", fi.Directory.FullName);
						loadedFolders.Add(appServerBinDirectory);
						loadedFolders.Add(fi.Directory.FullName);
					}

					//Load other assembly folders
					foreach (var a in assemblies)
					{
						var fi = new FileInfo(a.Location);
						if (!loadedFolders.Contains(fi.DirectoryName))
						{
							cp.CompilerOptions += String.Format(" /lib:\"{0}\"", fi.DirectoryName) + " ";
						}
					}

				}
				cp.ReferencedAssemblies.Add("System.dll");
				cp.ReferencedAssemblies.Add("System.Core.dll");

				var dalInDefault = false;
				var dalassembly = Path.Combine(appServerBinDirectory, businessNamespace.Remove(businessNamespace.Length - 9) + ".dll");
				if (File.Exists(dalassembly))
				{
					dalInDefault = true;
					var assemblyNH = Path.Combine(appServerBinDirectory, businessNamespace.Remove(businessNamespace.Length - 9) + ".dll");
					cp.ReferencedAssemblies.Add(assemblyNH);
					//cp.ReferencedAssemblies.Add(businessNamespace.Remove(businessNamespace.Length - 9) + ".dll");
				}

				foreach (var a in assemblies)
				{
					cp.ReferencedAssemblies.Add(a.Location);
				}

				if (dalInDefault)
				{
					cp.ReferencedAssemblies.Add("Widgetsphere.Core.dll");
				}
				else
				{
					var coreassembly = Path.Combine(appServerBinDirectory, "Widgetsphere.Core.dll");
					if (File.Exists(coreassembly))
						cp.ReferencedAssemblies.Add(coreassembly);
				}

				cp.GenerateExecutable = false;
				cp.GenerateInMemory = true;
				var cr = codeDomProvider.CompileAssemblyFromSource(cp, sb.ToString());
				if (cr.Errors.HasErrors)
				{
					var error = new StringBuilder();
					error.Append("Error Compiling Expression: ");
					foreach (CompilerError err in cr.Errors)
					{
						error.AppendFormat("{0}\n", err.ErrorText);
					}
					throw new Exception("Error Compiling Expression: " + error.ToString());
				}
				var assembly = cr.CompiledAssembly;
				var types = assembly.GetTypes();
				if (types.Length == 1)
					return types[0].GetMethod("ZZ" + guid);
			}
			catch (Exception ex)
			{
				throw ex;
			}
			return null;
		}

		private static MethodInfo CreateScriptClassAll(System.Guid currentGuid, string businessNamespace, string linq, string objectName)
		{
			var guid = currentGuid.ToString("N");
			var sb = new StringBuilder();
			sb.AppendLine("using System;");
			sb.AppendLine("using System.Linq;");
			sb.AppendLine("using System.Linq.Expressions;");
			sb.AppendLine("using " + businessNamespace + ".Objects;");
			sb.AppendLine("using " + businessNamespace + ".LINQ;");
			sb.AppendLine("namespace Widgetpshere.MemoryGeneration");
			sb.AppendLine("{");
			sb.AppendLine("public static class " + objectName + "Queries" + guid);
			sb.AppendLine("{");
			sb.AppendLine("    public static " + objectName + "Collection ZZ" + guid + "()");
			sb.AppendLine("    {");
			sb.AppendLine("        return " + objectName + "Collection.RunSelect(" + linq + ");");
			sb.AppendLine("    }");
			sb.AppendLine("}");
			sb.AppendLine("}");

			try
			{
				var codeDomProvider = CodeDomProvider.CreateProvider("C#");
				var cp = new CompilerParameters();
				//BusinessClass Assembly
				var businessAssembly = Assembly.LoadWithPartialName(businessNamespace.Remove(businessNamespace.Length - 9));

				var appServerBinDirectory = ConfigurationManager.AppSettings["AppServerBinDirectory"];
				if (string.IsNullOrEmpty(appServerBinDirectory))
				{
					var uri = new Uri(Assembly.GetExecutingAssembly().CodeBase);
					var fi = new FileInfo(uri.LocalPath);
					appServerBinDirectory = fi.Directory.FullName;
				}

				if (!string.IsNullOrEmpty(appServerBinDirectory))
				{
					//cp.CompilerOptions += string.Format(@",/lib:""{0}""", @"C:\WINDOWS\Assembly\gac_msil\LivePA.Portal\1.0.0.0__1c626ba0d40db45d");
					if (!appServerBinDirectory.ToLower().Contains(@"windows\assembly\gac_msil") && !appServerBinDirectory.ToLower().Contains(@"microsoft.net\assembly"))
						cp.CompilerOptions = string.Format(@"/lib:""{0}""", appServerBinDirectory);
					else
					{
						var uri = new Uri(businessAssembly.CodeBase);
						var fi = new FileInfo(uri.LocalPath);
						cp.CompilerOptions = String.Format("/lib:\"{0}\"", appServerBinDirectory) + String.Format(" /lib:\"{0}\"", fi.Directory.FullName);
					}

				}

				cp.ReferencedAssemblies.Add("System.dll");
				cp.ReferencedAssemblies.Add("System.Core.dll");
				cp.ReferencedAssemblies.Add(businessNamespace.Remove(businessNamespace.Length - 9) + ".dll");
				cp.ReferencedAssemblies.Add("Widgetsphere.Core.dll");
				cp.GenerateExecutable = false;
				cp.GenerateInMemory = true;
				var cr = codeDomProvider.CompileAssemblyFromSource(cp, sb.ToString());
				if (cr.Errors.HasErrors)
				{
					var error = new StringBuilder();
					error.Append("Error Compiling Expression: ");
					foreach (CompilerError err in cr.Errors)
					{
						error.AppendFormat("{0}\n", err.ErrorText);
					}
					throw new Exception("Error Compiling Expression: " + error.ToString());
				}
				var assembly = cr.CompiledAssembly;
				var types = assembly.GetTypes();
				if (types.Length == 1)
					return types[0].GetMethod("ZZ" + guid);
			}
			catch (Exception ex)
			{
				throw ex;
			}
			return null;
		}

	}

}